﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;


namespace OrderAggregator.Core
{
    public class ExcelExporter
    {
       

        private static readonly XNamespace SpreadsheetNS = "urn:schemas-microsoft-com:office:spreadsheet";
        private static readonly XNamespace MiniReporterNS = "urn:MiniReporter";
        
        private static readonly XName BindingTag = MiniReporterNS + "Binding";
        private static readonly XName DataBandTag = MiniReporterNS + "DataBand";

        private const string IDAttr = "ID";
        private const string PathAttr = "Path";
        private const string SourceAttr = "Source";

        
        


        public ExcelExporter()
        {
           
        }

        /// <summary>
        /// Export
        /// </summary>
        /// <param name="outFilePath"></param>
        /// <param name="dataObject"></param>
        public void Export(
            string outFilePath,
            object dataObject
            )
        {

            var template = LoadTemplate();

            var formater = new ExcelFormater();

            BindData(template, dataObject);

            formater.Complete(template);

            template.Save(outFilePath);
        }

        private static XDocument LoadTemplate()
        {
            var templateStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
                "OrderAggregator.ReportTemplates.XmlXlsTemplate.xml");

            using (var textReader = new StreamReader(templateStream))
            {
                return XDocument.Load(textReader);
            }
        }

        private static void BindData(XDocument report, object dataObject)
        {
            var dataContext = new DataContext();
            dataContext.SetValue(report.Root, "", dataObject);
           
            while (true)
            {
                var dataBand = report.Descendants(DataBandTag).FirstOrDefault();
                if (dataBand == null)
                {
                    break;
                }

                var id = dataBand.Attribute(IDAttr).Value;
                var path = dataBand.Attribute(PathAttr).Value;
                var values = dataContext.GetValue(dataBand, "", path) as System.Collections.IEnumerable;

                var repeatedTags = dataBand.Elements().ToArray();

                
                if (values != null)
                {
                    int i = 0;
                    foreach (var value in values)
                    {
                        foreach (var repeatedTag in repeatedTags)
                        {
                            var tag = new XElement(repeatedTag);

                            dataContext.SetValue(tag, id + ".Item", value);
                            dataContext.SetValue(tag, id + ".Number", i);

                            dataBand.AddBeforeSelf(tag);
                        }
                        i++;
                    }
                }
                
                dataBand.Remove();
            }


            var bindings = report.Descendants(BindingTag).ToArray();
            
            foreach (XElement binding in bindings)
            {
                var attr = binding.Attribute(SourceAttr);
                var source = attr == null ? string.Empty : attr.Value;

                attr = binding.Attribute(PathAttr);
                var path = attr == null ? string.Empty : attr.Value;
                
                var value = dataContext.GetValue(binding, source, path);

                var parent = binding.Parent;
                binding.Remove();

                parent.Value = value == null ? string.Empty : value.ToString();
            }
        }
        
        class DataContext
        {
            private readonly Dictionary<XElement, Dictionary<string, object>> _valuesHash =
                new Dictionary<XElement, Dictionary<string, object>>();

            public object GetValue(XElement context, string source, string path)
            {
                while (context != null)
                {
                    Dictionary<string, object> values;
                    if (_valuesHash.TryGetValue(context, out values))
                    {
                        object dataSource;
                        if (values.TryGetValue(source, out dataSource))
                        {
                            return GetValue(dataSource, path);
                        }
                    }

                    context = context.Parent;
                }

                return null;
            }

            public void SetValue(XElement context, string name, object value)
            {
                Dictionary<string, object> values;
                if (!_valuesHash.TryGetValue(context, out values))
                {
                    values = new Dictionary<string, object>();
                    _valuesHash[context] = values;
                }
                values[name] = value;
            }

            
            private static object GetValue(object dataObject, string path)
            {
                if (string.IsNullOrEmpty(path))
                {
                    return dataObject;
                }

                if (dataObject == null)
                {
                    return null;
                }

                var members = path.Split('.');

                for (int i = 0; i < members.Length; i++)
                {
                    if (dataObject == null)
                    {
                        return null;
                    }

                    var propInfo = dataObject.GetType().GetProperty(members[i]);

                    if (propInfo == null)
                    {
                        return null;
                    }

                    dataObject = propInfo.GetValue(dataObject, new object[0]);
                }

                return dataObject;
            }
        }

        class ExcelFormater
        {
            
            public void Complete(XDocument template)
            {
                foreach (var table in template.Descendants(SpreadsheetNS + "Table"))
                {
                    int rowsCount = table.Descendants(SpreadsheetNS + "Row").Count();
                    table.SetAttributeValue(SpreadsheetNS + "ExpandedRowCount", rowsCount);
                }
            }
        }
    }
}
